home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 April: Mac OS SDK / Dev.CD Apr 96 SDK / Dev.CD Apr 96 SDK1.toast / Development Kits (Disc 1) / OpenDoc / Sample Code / Scripting / Scripting in OpenDoc™ < prev    next >
Encoding:
Text File  |  1995-12-15  |  19.0 KB  |  479 lines  |  [TEXT/ttxt]

  1. Scripting in OpenDoc™
  2. By The OpenDoc Engineering Team
  3. December 15, 1995
  4.  
  5.  
  6. © 1993-1995  Apple Computer, Inc. All Rights Reserved.
  7. Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
  8. Mac and OpenDoc are trademarks of Apple Computer, Inc.
  9. ----------------------------------------------------------------------------------------------------------------------------
  10.  
  11.  
  12. This document is a very hasty attempt to give an overview of what scripting in OpenDoc means to a Part Editor developer.  The sample code at the end has been revised to reflect changes in OpenDoc for DR4, but the document is otherwise nearly the same as earlier versions.
  13.  
  14. For additional information, look in the Recipes folder inside the Documentation folder.
  15.  
  16. First we'll look at how you address a part via AppleScript.  Then we'll look at the code your part needs to have to make it happen.
  17.  
  18. From AppleScript:
  19.  
  20. The current versions of AppleScript (1.0 or 1.1) don't know anything about OpenDoc.  They expect you to address OpenDoc documents as if they were applications.  Thus you begin a script
  21.  
  22. Tell application "My OpenDoc Document"
  23.  
  24. The terminology you need to talk about parts lives in the OpenDocLib library (where you can't read it with dictionary browsers, and whence GetAETE event handlers retrieve it for the AppleScript compiler on demand.)  The only new class you'll need is "part."  But you don't even need that to talk to the root part, as it is synonymous with the document where properties and elements of parts are concerned.  Thus to get the color of the root part (assuming a root part that supports the color property), you say:
  25.  
  26. Tell application "My OpenDoc Document"
  27.     color
  28. end tell
  29.  
  30. While parts have the option of supporting access to their embedded parts any way they see fit, three forms of access are supported for *all* parts through the Default Semantic Interface (DSI).  Object accessors within the DSI make it possible to script parts that are embedded within non-scriptable parts.  (The DSI also provides Get/SetData handlers for a group of properties corresponding to those available through the Part/Frame Info dialog.)  Default access is provided by index, by name, and by (document-unique) persistent ID.  And you can get both the name (which the user can set) and the ID from a part (via the default GetData handler, of course):
  31.  
  32. Tell application "My OpenDoc Document"
  33.     set theName to name of part 1        --  part 1 is the first embedded part
  34.     set theID to ID of part theName      --  this is the same part, but gotten by name
  35.     tell part ID theID                            --  ID is a persistent reference the user can't change
  36.         set comments to "OpenDoc is here!"                            
  37.     end tell
  38. end tell
  39.  
  40. Access by both name and ID work anywhere within the specified container.  Once you have a valid name or ID, you can use it to refer to a part specifying only the document (application) as its container.  The default accessors will return an error, however, if you specify a container that is not valid -- so don't do this unless it makes sense.
  41.  
  42.     tell part ID theID of part ID theID       --  IDs are unique, so this is guaranteed to fail
  43.         doSomething()
  44.    end tell
  45.  
  46. Because the root part is the same as the document where AppleScript is concerned, there is no way to get it by index ("Part 1" means the first part embedded within the root.)  Thus you have to get the ID or name of the root part and use this to refer to it in cases where you need a specific reference to the part:
  47.  
  48. Tell application "My OpenDoc Document"
  49.     set theName to name
  50.     tell part theName to doSomething()
  51.     set theID to ID
  52.    tell part ID theID to doSomething()
  53. -- or simply
  54.    tell part name to doSomething()   -- ""name" means the name of the document/root part
  55.    tell part ID ID to doSomething()   --  the second "ID" means the ID of the document/root part
  56.  
  57. -- simplest of all, of course, since the document *is* the root part for scripting
  58.   DoSomething()
  59. end tell
  60.  
  61. For a more extensive example -- a script making use of lists and testing and setting various properties of parts within a complicated document -- see the script "OpenDoc Demo Script" somewhere near this file on your CD.
  62.  
  63. All of the above examples, excepting the one asking for "color," work on any document whether its parts are scriptable or not.  But for users to be able to do anything interesting or useful via scripting they'll need scriptable parts.  Which is what the next section is intended to get you started writing.
  64.  
  65. Within OpenDoc:
  66.  
  67. In order to make your part scriptable, you first need to instantiate a subclass of the ODSemanticInterface class.  One example of such a class is ODCPlusSemanticInterface.  However, your part class must have its own version of this class with a unique name.  To create one:
  68.  
  69. 1.  Use the PartMaker document "Create Semantic Interface". For more information on PartMaker, see the folder "PartMaker 4.4"
  70.  
  71. 2.  Put the generated .cpp file into your project and place the .xh and .xih file in a folder where your compiler can find them. The .idl file that is generated is only included for your information. You won't need to use this file unless you plan to alter the SOM class for your own purposes.
  72.  
  73. You may have to make some other minor changes to these files, depending on your exception handling scheme and the like.
  74.  
  75. 3.  In your part initialization code, include the following calls:
  76.  
  77.     _fSemtIntf = new KilroySI;
  78.     SIHelper* semItfcHelper = new SIHelper;
  79.  
  80.     semItfcHelper->InitSIHelper(_fSemtIntf);
  81.     _fSemtIntf->InitCPlusSemanticInterface(ev, somSelf, semItfcHelper, _fSession);
  82.  
  83. (where _fSemtIntf is  field of your part class used to hold a reference to your semantic interface).
  84.  
  85. 4.  semItfcHelper, the SIHelper object instantiated above, is used to install your part’s object accessors, event handlers, and other AppleEvent procs.
  86.  
  87. 5.  Your part needs to implement HasExtension and GetExtension calls to supply your semantic interface where needed.  Typical code would be: 
  88.  
  89.     ODBoolean    KilroyPart::HasExtension(Environment *ev, ODType extensionName)
  90.     {
  91.         if (!ODISOStrCompare( (ODISOStr)extensionName, kODExtSemanticInterface ))
  92.             return kODTrue;
  93.         else
  94.             return kODFalse;
  95.     }
  96.  
  97.  
  98.     ODExtension*    KilroyPart::GetExtension(Environment *ev, ODType extensionName)
  99.     {
  100.         if (!ODISOStrCompare( (ODISOStr)extensionName, kODExtSemanticInterface ))
  101.             return _fSemtIntf;
  102.         else
  103.             return kODNULL;
  104.     }
  105.  
  106. If you've worked with Apple Events before in a "normal" (pre-OpenDoc) application, you're probably familiar with the Object Model and the use of accessors and the Object Support Library to resolve object specifiers.  Object Specifiers within the OpenDoc context work much the same way, but with the additional complication that parts (or the DSI acting on their behalf) must cooperate to help the OSL "swap" across part boundaries.  In an application, the app itself is the "null" container; in OpenDoc, each part in the containment hierarchy starts as "null" when its turn comes.
  107.  
  108. Also different in OpenDoc is the order in which object resolution and event dispatch are done.  Applications call AEProcessAppleEvent, which dispatches the event to the right handler.  Any object specifiers within the event are resolved using the OSL's AEResolve() call.  But OpenDoc begins by resolving the direct parameter itself (if present); only after doing so does it know to which part it should dispatch the event.
  109.  
  110. Consider the object specifier
  111.  
  112. color of part "fred" of part 1 of part ID 6 of application "my OpenDoc Doc"
  113.  
  114. Though this can be resolved entirely with DSI object accessors, it's useful to consider the order in which the parts involved get accessor calls if they're scriptable.  First is the root part (which corresponds to the application here): it gets asked for part ID 6 from "null".  And once it returns a token representing that part it gets asked for part 1 of that token.  Since it cannot get anything out of a part it contains (in general, anyway), it "swaps," telling OpenDoc to ask another part (the one for which it has a token, part ID 6) instead.
  115.  
  116. And so OpenDoc calls that part's accessor for part 1 from null.  And so it continues: that part will return a token for part 1, but on being asked for part "fred" will swap so that part 1 gets asked for part "fred."  After yet another swap, part "fred" will be asked for a token for the property "color" and object resolution will be over.  Only now will the GetData event handler installed by part "fred" be called, the token for property color having replaced the object specifier in its direct parameter.
  117.  
  118. Here is some sample code drawn from the object accessors and event handlers within the DSI.  Consider it for its illustrative value only, since the fact that it's in the DSI means you'll never need to duplicate it inside your own part editor.  Also be warned that this code has never been compiled: it's close to what's in our sources but the changes I made moving it here may well have broken something.
  119.  
  120. Default accessors first:
  121.  
  122. //------------------------------------------------------------------------------
  123. // GetPropFromNULL
  124. // First check that the property being sought is one that we are able
  125. // to provide.  If it is, return a token for it.  Otherwise barf.
  126. //
  127. // Note that here in a DSI accessor barfing is the end of the line,
  128. // but if this were a part's accessor *and* it returned errAEEventNotHandled
  129. // instead of errAENoSuchObject, the DSI would respond by trying to access
  130. // the property.  Thus parts that want to provide access to their own
  131. // properties, but don't want to bother duplicating code for those listed
  132. // below, can "inherit" the DSI's features by returning errAEEventNotHandled.
  133. //------------------------------------------------------------------------------
  134.  
  135. ODError GetPropFromNULL(
  136.         ODPart*            scriptlessPart,
  137.         DescType        desiredClass,
  138.         ODOSLToken*        container,
  139.         DescType        containerClass,
  140.         DescType        form,
  141.         ODDesc*            selectionData,
  142.         ODOSLToken*        value,
  143.         ODSLong            refCon)
  144. {
  145.     ODError result = noErr ;
  146.     Environment        *ev = somGetGlobalEnvironment();
  147.     ODNameResolver* resolver = ((ODSession*)refCon)->GetNameResolver(ev);
  148.     AEDesc realSelData = {typeNull, kODNULL};
  149.  
  150.     TRY
  151.         if ( (!resolver->IsODToken( ev, value )) || (form != formPropertyID) )
  152.             THROW( errAENoSuchObject );
  153.     
  154.         THROW_IF_ERROR( ODDescToAEDesc(selectionData, &realSelData ));
  155.     
  156.         switch ( **(DescType**)(realSelData.dataHandle) )
  157.         {    
  158.             case pName:
  159.             case pComment:
  160.             case pAuthor:
  161.             case pKind:
  162.             case pEditorName:
  163.             case pBundled:
  164.             case pStationery:
  165.             case pShowLinks:
  166.             case pASCreationDate:
  167.             case pASModificationDate:
  168.             case pSize:
  169.             case pUniqueID:
  170.             case pIcon:
  171.             case pViewType:
  172.             case pContainer:
  173.             case pCategory:
  174.             case pClass:
  175.             case pDefaultType:
  176.             case pBestType:
  177.                 break;
  178.             default:
  179.                 result = errAENoSuchObject;
  180.         }
  181.     
  182.         if ( result == noErr )
  183.         {
  184.             // copy the property code into the result token.
  185.             ODDesc* userTokenAsODDesc = resolver->GetUserToken(ev, value);
  186.             THROW_IF_ERROR( AEDescToODDesc( &realSelData, userTokenAsODDesc ));
  187.             userTokenAsODDesc->SetDescType(ev, cProperty);
  188.         }
  189.     
  190.     CATCH_ALL
  191.         result = ErrorCode();
  192.     ENDTRY
  193.  
  194.     AEDisposeDesc( &realSelData );        // dispose local copy
  195.     return result;
  196. }
  197.  
  198.  
  199. //------------------------------------------------------------------------------
  200. // GetPartFromNULL
  201. // We're being asked to find a part within our own content.  We do so and
  202. // return a token for it.  Note that what the user calls a "part" we designate
  203. // with an ODFrame*, since that's usually what is meant by "part" and it's
  204. // trivial to get the ODPart* if necessary by calling ODFrame's AcquirePart()
  205. // method.
  206. //------------------------------------------------------------------------------
  207.  
  208. ODError GetPartFromNULL(
  209.         ODPart*            scriptlessPart,
  210.         DescType        /*desiredClass*/,        // not used
  211.         ODOSLToken*        /*container*/,            // not used
  212.         DescType        /*containerClass*/,        // not used
  213.         DescType        form,
  214.         ODDesc*            selectionData,
  215.         ODOSLToken*        value,
  216.         ODSLong            refCon)
  217. {
  218.     Environment        *ev = somGetGlobalEnvironment();
  219.     ODNameResolver*    resolver = ((ODSession*)refCon)->GetNameResolver(ev);
  220.     ODUShort         i;
  221.     ODError            error = noErr;
  222.  
  223.     if( !resolver->IsODToken( ev, value ) )
  224.         return errAENoSuchObject;
  225.     
  226.     AEDesc realSelData = {typeNull, NULL};
  227.     AEDesc coercedSelData = {typeNull, NULL};
  228.  
  229.     ODVolatile(realSelData);        // not necessary with our compiler....
  230.  
  231.     TRY
  232.  
  233.     ODFrame* containingFrame;
  234.     ODPart* ignorePart;
  235.     resolver->GetContextFromToken(ev, value, &ignorePart, &containingFrame);
  236.  
  237.     ODSLong partCount = CountEmbeddedParts(ev, scriptlessPart);
  238.     THROW_IF_ERROR( ODDescToAEDesc(selectionData, &realSelData ));
  239.  
  240.     switch (form)
  241.     {
  242.         case formName:
  243.             THROW_IF_ERROR( AECoerceDesc( &realSelData, typeIntlText,
  244.                     &coercedSelData ) );
  245.             ODIText* name = ITextFromIntlDesc( &coercedSelData );
  246.             AEDisposeDesc( &coercedSelData );
  247.  
  248.             ODFrame* resultFrame = kODNULL;
  249.             if ( !containingFrame )        // if shell, start with root part
  250.             {
  251.                 containingFrame =  GetDefaultRootFrameToSwapTo(ev,
  252.                         (ODSession*)refCon);
  253.                 ODIText* rootName = ODGetPartName(ev, containingFrame,
  254.                         kODNULL);
  255.                 if ( ITextEqual( rootName, name ) )
  256.                     resultFrame = containingFrame ;
  257.             }
  258.             if ( !resultFrame )
  259.                 resultFrame = AcquireFrameWithName( ev, containingFrame, name );
  260.             PutFrameInValue( ev, resolver, value, resultFrame );
  261.             ODReleaseObject(ev, resultFrame);
  262.             break;
  263.             
  264.         case formUniqueID:
  265.             THROW_IF_ERROR( AECoerceDesc( &realSelData, typeLongInteger,
  266.                     &coercedSelData ) );
  267.             ODPersistentObjectID objectID =
  268.                     **(ODPersistentObjectID**)(coercedSelData.dataHandle);
  269.             AEDisposeDesc( &coercedSelData );
  270.  
  271.             ODFrame* tmpFrame = containingFrame ? containingFrame :
  272.                     GetDefaultRootFrameToSwapTo(ev, (ODSession*)refCon);
  273.             ODDraft* draft = tmpFrame->GetStorageUnit(ev)->GetDraft(ev);
  274.             ODObjectType objectType;
  275.             tmpFrame = (ODFrame*)draft->AcquirePersistentObject(
  276.                     ev, objectID, &objectType);
  277.             if ( !tmpFrame || (strcmp( kODFrameObject, objectType ) != 0)
  278.                     || (!PartIsContained( ev, tmpFrame, containingFrame )) )
  279.                 error = errAENoSuchObject;
  280.             else
  281.                 PutFrameInValue( ev, resolver, value, tmpFrame );
  282.             ODReleaseObject(ev, tmpFrame);
  283.             break;
  284.  
  285.         case formAbsolutePosition:    // delete to simplify code.
  286.         default:
  287.             THROW(errAENoSuchObject);
  288.             break;
  289.     }
  290.         
  291.     
  292.     CATCH_ALL
  293.     error = ErrorCode();
  294.     ENDTRY
  295.     
  296.     (void)AEDisposeDesc( &realSelData );
  297.     return error;
  298. }
  299.  
  300. //------------------------------------------------------------------------------
  301. // GetWildcardFromPart
  302. // This accessor doesn't attempt to get anything out of a part it contains
  303. // (though in reality some properties, such as the bounding rect, are
  304. // available to a container.  It simply "swaps" to the contained part.
  305. //------------------------------------------------------------------------------
  306.  
  307. ODError GetWildcardFromPart(
  308.         ODPart*            /*scriptlessPart*/,        // not used
  309.         DescType        /*desiredClass*/,        // not used
  310.         ODOSLToken*        container,
  311.         DescType        /*containerClass*/,        // not used
  312.         DescType        /*form*/,                // not used
  313.         ODDesc*            /*selectionData*/,        // not used
  314.         ODOSLToken*        value,
  315.         ODSLong            refCon)
  316. {    
  317.     Environment *ev = somGetGlobalEnvironment();
  318.     ODError result;
  319.     OSLToken realToken = {typeNull, NULL};
  320.     ODNameResolver* resolver = ((ODSession*)refcon)->GetNameResolver(ev);
  321.  
  322.     TRY
  323.         ODDesc* tokenWrapper = resolver->GetUserToken(ev, container);
  324.         THROW_IF_ERROR( ODDescToAEDesc( tokenWrapper, &realToken ));
  325.  
  326.         ODPart* part;
  327.         ODFrame* frame;
  328.         PartFrameFromStandardPartToken( &realToken, &part, &frame );
  329.         resolver->CreateSwapToken(ev, value, part, frame );
  330.         result = noErr;
  331.     CATCH_ALL
  332.         result = errAENoSuchObject;
  333.     ENDTRY
  334.     (void)AEDisposeDesc( &realToken );
  335.     return result;
  336. }
  337.  
  338. Then an event handler:
  339.  
  340. //------------------------------------------------------------------------------
  341. // HandleGetData
  342. // Take a token for the direct parameter, get the property out of it,
  343. // and return the data associated if it's available.
  344. //------------------------------------------------------------------------------
  345.  
  346. ODError HandleGetDataInternal(    ODPart* thePart,
  347.                                 ODAppleEvent* message,
  348.                                 ODAppleEvent* reply,
  349.                                 ODSLong    refCon)
  350. {
  351.     Environment *ev = somGetGlobalEnvironment();
  352.     ODError err = noErr;
  353.     ODSession* session = (ODSession*)refCon;
  354.  
  355.     OSLToken evtDp;
  356.     GetDirectParam( session, message, &evtDp );
  357.                                 
  358.     if ( evtDp.descriptorType == typeAEList )
  359.     {
  360.         // left as an exercise for the user :-)        
  361.     }
  362.     else
  363.     {
  364.         if ( !thePart )
  365.             return errAEEventNotHandled;
  366.  
  367.         AEDesc result = {typeNull, NULL};
  368.  
  369.         // find out what type was requested
  370.         // e.g. get foo as alias
  371.         DescType    desiredType, theType;
  372.         long        theSize;
  373.         AppleEvent    theMessage;
  374.         THROW_IF_ERROR( ODDescToAEDesc(message, &theMessage) );
  375.         err = AEGetParamPtr( &theMessage, keyAERequestedType, typeType,
  376.                 &theType, &desiredType, sizeof(desiredType), &theSize );
  377.         AEDisposeDesc(&theMessage);
  378.         if ( err != noErr )
  379.             desiredType = typeWildCard;
  380.  
  381.         switch( evtDp.descriptorType )
  382.         {
  383.             case cPart:
  384.                 // create an object specifier for the part: "part ID <id>"
  385.                 err = CreateObjSpecForFrame( ev,
  386.                         FrameFromStandardPartToken(&evtDp), &result );
  387.                 break;
  388.  
  389.             case cProperty:
  390.             {
  391.                 ODBoolean boolResult;
  392.                 ODIText* itext = kODNULL;
  393.         
  394.                 ODStorageUnit* partSU = ODGetSUFromPstObj(ev, thePart);
  395.                 
  396.                 DescType property = **(DescType**)(evtDp.dataHandle);
  397.                 
  398.                 switch( property )
  399.                 {
  400.                     // text properties; rest left out for clarity
  401.                     case pComment:
  402.                     case pAuthor:
  403.                     case pKind:
  404.                     case pEditorName:
  405.                     {
  406.                         ODBoolean okToDispose = kODTrue;
  407.                         if ( property == pComment )
  408.                         {
  409.                             itext = ODGetPOComments(ev, thePart, kODNULL );
  410.                             if ( !itext )
  411.                             {
  412.                                 // script and language code don't matter here; stripped below
  413.                                 itext = CreateITextCString( 0, 0, "" );
  414.                                 okToDispose = kODTrue;
  415.                             }
  416.                         }
  417.                         else if ( property == pAuthor )
  418.                             itext = ODGetModifiedBy(ev, partSU, kODNULL );
  419.                         else if ( property == pKind )
  420.                         {
  421.                             ODType theType = ODGetKind(ev, thePart );
  422.                             okToDispose = !GetUserKindFromKind(
  423.                                     session->GetNameSpaceManager(ev), theType, &itext );
  424.                         }
  425.                         else if ( property == pEditorName )
  426.                         {
  427.                             ODEditor editor = ((ODPartWrapper*)thePart)->GetEditor(ev);
  428.                             okToDispose = !GetUserEditorFromEditor(
  429.                                     session->GetNameSpaceManager(ev), editor, &itext );
  430.                         }
  431.             
  432.                         if ( itext )
  433.                         {
  434.                             err = AECreateDesc( typeChar, GetITextPtr( itext ),
  435.                                     GetITextStringLength( itext ), &result );
  436.                             if ( okToDispose )
  437.                                 DisposeIText(itext);
  438.                         }
  439.                         else
  440.                             err = errAEEventNotHandled;
  441.                         break;
  442.                     }
  443.                     
  444.                     default:
  445.                         err = errAEEventNotHandled;
  446.                 }
  447.                 break;    // case cProperty:
  448.             }
  449.             default:
  450.                 err = errAEEventNotHandled;
  451.         }
  452.         
  453.         if ( !err )
  454.         {
  455.             // do we need to coerce the result?
  456.             if (desiredType != typeWildCard &&
  457.                     desiredType != result.descriptorType)
  458.             {
  459.                 AEDesc    newDesc;
  460.                 err = AECoerceDesc(&result, desiredType, &newDesc);
  461.                 if (err != noErr)
  462.                 {
  463.                     // we can't coerce to the desiredType, so we should
  464.                     // provide a decent error message and throw the error...
  465.                 }
  466.             }    
  467.         }
  468.  
  469.         if ( !err )
  470.         {
  471.             ODPutParamDesc( reply, keyDirectObject, &result );
  472.             AEDisposeDesc( &result );
  473.         }
  474.  
  475.     }
  476.     (void)AEDisposeDesc( &evtDp );
  477.     return err;
  478. }
  479.